/*
 * Copyright 2008 Slava Chernyak
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */
package com.chernyak.DataAdapter;

import java.io.FileReader;
import java.util.*;

import com.chernyak.DataAdapter.MessageSink.CompilerMessage;

/* TODO list:
 *  - Typechecking is currently O(n^2) since we are using lists. Switch it
 *    to using maps so that we can make it O(n).
 *  - Use getopts
 *  - re-init fields when reading from stream to clear object state...
 *  - restrict symbols starting with '__da_' from lexer
 *  - add getters and setters
 *  - add support for primitive 'option' types
 */

/** @author Slava Chernyak
 * 
 * This is the main class for the Data Adapter compiler. Use this class to 
 * generate Data Adapter.
 * 
 * Here is an example of how to use this compiler. Consider the following input 
 * data adapter specification:
 * 
 * MyWritableComparable {
 *  int compare id;
 *  char someChar;
 *  String myString;
 * }
 * 
 * Let's say you store this declaration in a file 'mywc.da'. To compile it to a Hadoop
 * WritableComparable class you would invoke the main method of this class with the
 * filename as an argument. For example:
 * 
 * java com.chernyak.DataAdapter.DataAdapterGenerator mywc.da
 * 
 * The result will be a file called 'MyWritableComparable.java' that will declare a 
 * MyWrtiableComparable class that implements WritableComparable and has the fields 
 * as declared in the declaration above. It will implement the following methods:
 * 
 *  int compareTo(MyWritableComparable)
 *  boolean equals(Object o)
 *  int hashCode()
 *  void write(DataOutput)
 *  void readFields(DataInput)
 *  static MyWritableComparable parse(String s, String delim)
 *  String toString()
 * 
 */
public class DataAdapterGenerator {
	ArrayList<DataAdapterDecl> adapterDecls;
	String sourceFile;
	
	/** Generate data adapter to files 
	 * 
	 * @param args takes one argument - input specification
	 */
	public static void main(String[] args) {
		int errors = 0;
		int warnings = 0;
		int filesWritten = 0;
		
		try {
			Map<String, String> files = doGenerate(args[0], true);
			for (String fileName : files.keySet()) {
				String fileContents = files.get(fileName);
				Common.writeFile(fileContents, fileName);
				filesWritten++;
			}
		} catch (CompilerException e) {
			errors++;
			System.err.println("ERROR: (" + e.getLineNumber() + ") " + 
					e.getMessage());
		}
		
		errors += MessageSink.getErrors().length;
		warnings += MessageSink.getWarnings().length;
		
		for(CompilerMessage m : MessageSink.getErrors()) {
			System.err.println(m.toString());
		}
		
		for(CompilerMessage m : MessageSink.getWarnings()) {
			System.err.println(m.toString());
		}

		System.out.println("Errors: " + errors + "\nWarnings: " + warnings + 
				"\nFiles Written: " + filesWritten);
	}
	
	/** Perform the generation of desired files
	 * 
	 * @param dataAdapterFileName input specification
	 * @param genWritableComparable generate WritableComparable classes
	 * @return Map of files and their contents
	 */
	public static Map<String,String> doGenerate(
			String dataAdapterFileName,
			boolean genWritableComparable) throws CompilerException {
		DataAdapterGenerator g = doParse(dataAdapterFileName);
		Map<String, String> files = 
			new HashMap<String,String>();
		
		if (g == null || g.adapterDecls == null) {
			return files;
		}
		
		for (DataAdapterDecl d : g.adapterDecls) {
			if (genWritableComparable) {
				String wcfile = generateWriteableComparable(g,d);
				String filename = d.generateWritableComparableFileName();
				files.put(filename, wcfile);
			}
		}
		return files;
	}
	
	/** Create a new empty generator
	 */
	public DataAdapterGenerator () {
		System.out.println("DEBUG");
		this.adapterDecls = new ArrayList<DataAdapterDecl>();
	}
	
	/** Create a new generator with specified declarations and line number 
	 * this is used by the CUP-generated parser 
	 * 
	 * @param adapterDecls declarations
	 * @param ln line number
	 */
	public DataAdapterGenerator (
			ArrayList<DataAdapterDecl> adapterDecls,
			int ln) {
		this.adapterDecls = adapterDecls;
	}
	
	/** Perform the parsing of the specification
	 * 
	 * @param file file containing the specification
	 * @return the generator on the output of the parse
	 */
	public static DataAdapterGenerator doParse(String file) {
		try {
			parser p = new parser(new DataAdapterLexer(new FileReader(file)));
			DataAdapterGenerator g = (DataAdapterGenerator)p.parse().value;
			g.typecheck();
			g.sourceFile = file;
			return g;
		} catch (Exception e) {
			if (e instanceof CompilerException) {
				throw (CompilerException)e;
			}
			e.printStackTrace(System.err);
			return null;
		}
	}
	
	/** Typecheck the parsed generator
	 */
	public void typecheck() {
		List<String> s = new ArrayList<String>();
		Map<String, GlobalSymbolProperties> gs = new HashMap<String, GlobalSymbolProperties>();
		for (DataAdapterDecl d : adapterDecls) {
			d.typecheck(gs, s, null);
		}
	}
	
	/** Generate a writable comparable class from specific data adapter 
	 * declaration
	 * 
	 * @param g generator
	 * @param d declaration
	 * @return generated class as string
	 */
	public static String generateWriteableComparable(DataAdapterGenerator g, DataAdapterDecl d) {
		StringBuilder sb = new StringBuilder();
		
		writeHeaderHelper(g, sb);
		
		d.lowerHeader(sb);

		d.lowerComparisonWCG(sb);
		d.lowerDeclarations(sb);
		d.lowerEquals(sb);
		d.lowerHashCode(sb);
		d.lowerParse(sb);
		d.lowerReadWCG(sb);
		d.lowerToString(sb);
		d.lowerWriteWCG(sb);
		
		d.lowerFooter(sb);
		
		String code = sb.toString();
		CodeFormatter f = new CodeFormatter();
		code = f.formatCode(code);
		
		return code;
	}
	
	/** Helper to write the header file for all generated sourcecode
	 * 
	 * @param g generator
	 * @param sb sink to append content to
	 */
	private static void writeHeaderHelper(DataAdapterGenerator g, StringBuilder sb) {
		sb.append("/* AUTOGENERATED: DO NOT EDIT\n");
		sb.append("   generated at:\n");
		sb.append("    " + 
				java.util.Calendar.getInstance().getTime().toString() + "\n");
		sb.append("   source data adapter definition:\n");
		sb.append("    " + g.sourceFile + "\n");
		sb.append("*/\n");
	}
	
	public String toString() {
		if (adapterDecls.size() != 0) {
			StringBuilder sb = new StringBuilder();
			sb.append("DataAdapter contents:\n");
			for (DataAdapterDecl d : adapterDecls) {
				sb.append(d.toString());
			}
			return sb.toString();
		 } else {
			return "DataAdapter DEBUG!"; 
		 }
		
	}
	
	/** Documentation of the DataAdapter format
	 * 
	 * fully.qualified.adapter.name {
	 * 	type1 field1;
	 *  type2 field2;
	 *  type3[] field3; // array maxsize defined by int
	 *  ...
	 *  typeN fieldN;
	 * }
	 * 
	 * types supported
	 *  byte 	- 8bit (1 byte)
	 *  short 	- 2 bytes
	 *  int 	- 4 bytes 
	 *  long 	- 8 bytes
	 *  float	- 4 bytes
	 *  double 	- 8 bytes
	 *  boolean	- 1 byte
	 *  char 	- 2 bytes
	 *  
	 *  String  - encoded as array of chars
	 *  
	 *  
	 *  In general types will behave as per java specification here:
	 *   http://java.sun.com/docs/books/tutorial/java/nutsandbolts/datatypes.html

	 */
}
